#!/usr/bin/env python
# -*- coding: utf-8 -*-

from typing import Any

from cta.config.cross_section_config.multi_factor_config import MultiFactorConfig
from cta.interface.action.open_position.abstract_open_position_action import AbstractOpenPositionAction
from web.constants.direction import Direction
from web.domain.responsibility_chain_dto import ResponsibilityChainDto
from web.manager.log_manager import LogManager
from web.models import CommodityFutureInfo
from web.models.commodity_future_date_contract_data import CommodityFutureDateContractData
from web.models.quant2_account import Quant2Account
from web.models.quant2_commodity_future_filter import Quant2CommodityFutureFilter
from web.models.quant2_commodity_future_transaction_record import Quant2CommodityFutureTransactionRecord
from web.util.commodity_future_code_util import CommodityFutureCodeUtil
from web.util.fee_util import FeeUtil

Logger = LogManager.get_logger(__name__)

class Quant2MultiFactorOpenPositionAction(AbstractOpenPositionAction):
    """
    多因子策略，开仓期货
    """

    def exec(self, responsibility_chain_dto: ResponsibilityChainDto = None) -> Any:
        Logger.info("多因子策略，开仓期货")

        transaction_date: str = responsibility_chain_dto.transaction_date

        # 根据持仓时间，判断k线形态；判断当天是否应当平仓和开仓
        # k_line_movement_pattern, close_or_open_position = self.quant2_k_line_movement_pattern_and_close_or_open_position_by_holding_position_time(transaction_date)
        # if not close_or_open_position:
        #     Logger.info("日期[%s]不应当平仓和开仓", transaction_date)
        #     super().next(responsibility_chain_dto)
        #     return

        # 查询quant2_c_f_filter表的记录，不包括已经持仓的记录，，并按照up_down_percentage_with_n_date列降序排列。然后创建升序数组
        waiting_long_quant2_commodity_future_filter_list: list[Quant2CommodityFutureFilter] = self.quant2_commodity_future_filter_dao.order_by_up_down_percentage_with_n_date_desc()
        if waiting_long_quant2_commodity_future_filter_list is None or len(waiting_long_quant2_commodity_future_filter_list) == 0:
            Logger.error("表quant2_c_f_filter为空，无法执行开仓操作")
            super().next(responsibility_chain_dto)
            return
        waiting_short_quant2_commodity_future_filter_list: list[Quant2CommodityFutureFilter] = waiting_long_quant2_commodity_future_filter_list[::-1]

        # 计算多头仓位和空头仓位应该是多少
        should_long_position_number, should_short_position_number = self.calculate_long_position_and_short_position(waiting_long_quant2_commodity_future_filter_list)
        Logger.info("多头应该的仓位为[%s]，空头应该的仓位为[%s]", should_long_position_number, should_short_position_number)

        # quant2_account表的所有记录
        quant2_account_list: list[Quant2Account] = self.quant2_account_dao.find_all()
        if quant2_account_list is not None and len(quant2_account_list):

            for quant2_account_index, quant2_account in enumerate(quant2_account_list):
                # 查询实际持仓期货中多头数量
                actual_long_position_number: int = self.find_quant2_actual_long_position_number(quant2_account.account_name, Direction.Up)
                # 查询实际持仓期货中空头数量
                actual_short_position_number: int = self.find_quant2_actual_short_position_number(quant2_account.account_name, Direction.Down)
                # 总持仓数量
                total_hold_commodity_future_number: int = quant2_account.hold_commodity_future_number

                # 如果某个账号的持有期货的数量已经到达最大持有期货的数量，则跳过这个账号
                if actual_long_position_number >= should_long_position_number \
                        or actual_short_position_number >= should_short_position_number \
                        or total_hold_commodity_future_number >= MultiFactorConfig.Max_Hold_Commodity_Future_Number:
                    Logger.info("账号[%s]的持有期货数量已经到达[%d]只，不再开仓期货", quant2_account.account_name, total_hold_commodity_future_number)
                    continue

                # 应该持有的多头和空头期货
                should_long_quant2_commodity_future_filter_list, should_short_quant2_commodity_future_filter_list = self.find_should_hold_long_and_short_commodity_future_filter_list(quant2_account_index, should_long_position_number, should_short_position_number)

                # 正在持有的期货
                holding_quant2_commodity_future_transaction_record_list: list[Quant2CommodityFutureTransactionRecord] = self.quant2_commodity_future_transaction_record_dao.find_by_account_name_and_not_close_position(quant2_account.account_name)

                # 查询实际持仓期货中多头数量
                actual_long_position_number: int = self.find_quant2_actual_long_position_number(quant2_account.account_name, Direction.Up)

                # 查询实际持仓期货中空头数量
                actual_short_position_number: int = self.find_quant2_actual_short_position_number(quant2_account.account_name, Direction.Down)

                # 开多仓
                if waiting_long_quant2_commodity_future_filter_list is not None and len(waiting_long_quant2_commodity_future_filter_list) > 0 \
                        and should_long_quant2_commodity_future_filter_list is not None and len(should_long_quant2_commodity_future_filter_list) > 0:
                    for waiting_quant2_commodity_future_filter in waiting_long_quant2_commodity_future_filter_list:

                        # 如果备选的期货不在应该持有的期货中，则跳过
                        if waiting_quant2_commodity_future_filter.code not in list(map(lambda x: x.code, should_long_quant2_commodity_future_filter_list)):
                            continue

                        # 如果当前持仓的期货已经持有，则跳过
                        if holding_quant2_commodity_future_transaction_record_list is not None and len(holding_quant2_commodity_future_transaction_record_list) > 0:
                            if waiting_quant2_commodity_future_filter.code in list(map(lambda x: x.code, holding_quant2_commodity_future_transaction_record_list)):
                                continue

                        # 查询commodity_future_info记录（大小写不敏感的精确匹配查询）
                        filter_dict = {'code__iexact': CommodityFutureCodeUtil.code_to_contract_code(waiting_quant2_commodity_future_filter.code)}
                        commodity_future_info: CommodityFutureInfo = self.commodity_future_info_dao.find_one(filter_dict, dict(), list())

                        # 查询期货记录
                        filter_dict = {'code': waiting_quant2_commodity_future_filter.code, 'transaction_date': transaction_date}
                        commodity_future_date_contract_data: CommodityFutureDateContractData = self.commodity_future_date_contract_data_dao.find_one(filter_dict, dict(), list())
                        if commodity_future_date_contract_data is None:
                            Logger.warning("期货在日期[%s]没有交易记录，从表quant2_c_f_filter表中删除这条记录，跳过这个期货", waiting_quant2_commodity_future_filter.code, transaction_date)

                            # 从quant2_c_f_filter表中删除这条记录
                            # self.quant2_commodity_future_filter_dao.delete(waiting_quant2_commodity_future_filter)

                            continue

                        # 根据持仓时间，判断k线形态；判断当天是否应当平仓和开仓
                        k_line_movement_pattern, close_or_open_position = self.quant2_k_line_movement_pattern_and_close_or_open_position_by_holding_position_time_and_commodity_future_date_contract_data(transaction_date, commodity_future_date_contract_data)
                        if close_or_open_position is False:
                            Logger.info("期货[%s]在日期[%s]不应当平仓和开仓", commodity_future_date_contract_data.code, transaction_date)
                            continue

                        # 如果期货的收盘价过高，或者资金资产太少，连一手也无法开仓，则直接查找下一个账号
                        if FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, 1) > quant2_account.capital_assets:
                            break

                        # 如果可以开仓，则向quant2_c_f_transact_record表中插入数据，从quant2_c_f_filter表中删除这条记录。
                        # 计算应该开仓期货的数量
                        # quant2_c_f_filter表的记录数（up_down_percentage_with_n_date字段大于等于0）
                        quant2_c_f_filter_number: int = -1
                        filter_dict = {'up_down_percentage_with_n_date__gte': 0}
                        quant2_c_f_filter_number = len(self.quant2_commodity_future_filter_dao.find_list(filter_dict, dict(), dict()))

                        # 应当开仓的期货的数量
                        should_open_position_commodity_future_number: int = -1
                        if quant2_c_f_filter_number <= (should_long_position_number - actual_long_position_number):
                            should_open_position_commodity_future_number = quant2_c_f_filter_number
                        else:
                            should_open_position_commodity_future_number = should_long_position_number - actual_long_position_number

                        # 判断是否需要继续开仓
                        if should_open_position_commodity_future_number == 0:
                            Logger.info("账号[%s]持有多头期货的数量已经是%d，不再需要开仓期货", quant2_account.account_name, actual_long_position_number)
                            break

                        # 计算开仓多少手
                        # 开仓多少手
                        init_open_or_close_position_lot: int = FeeUtil.calculate_open_commodity_future_lot(commodity_future_date_contract_data.close_price, commodity_future_info, float(quant2_account.capital_assets) / float(MultiFactorConfig.Max_Hold_Commodity_Future_Number - quant2_account.hold_commodity_future_number))
                        open_or_close_position_lot: int = init_open_or_close_position_lot if init_open_or_close_position_lot > 1 else 1
                        while float(quant2_account.capital_assets) / float(MultiFactorConfig.Max_Hold_Commodity_Future_Number - quant2_account.hold_commodity_future_number) >= FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, open_or_close_position_lot):
                            if FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, open_or_close_position_lot) >= float(quant2_account.capital_assets) / MultiFactorConfig.Max_Hold_Commodity_Future_Number:
                                break
                            open_or_close_position_lot = open_or_close_position_lot + 1
                        if float(quant2_account.capital_assets) / float(MultiFactorConfig.Max_Hold_Commodity_Future_Number - quant2_account.hold_commodity_future_number) < FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, open_or_close_position_lot):
                            if open_or_close_position_lot == 1:

                                continue
                            else:
                                open_or_close_position_lot = open_or_close_position_lot - 1

                        # 向表quant2_c_f_transact_record中插入记录
                        self.quant2_commodity_future_transaction_record_dao.insert_when_open_long_position(quant2_account.account_name, waiting_quant2_commodity_future_filter.code,
                            transaction_date, commodity_future_date_contract_data.close_price, open_or_close_position_lot, commodity_future_info, Direction.Up, k_line_movement_pattern)

                        # 删除quant2_c_f_filter表中的记录
                        # self.quant2_commodity_future_filter_dao.delete(quant2_commodity_future_filter)

                        # 更新账号的持有期货的数量
                        total_hold_commodity_future_number = total_hold_commodity_future_number + 1
                        actual_long_position_number = actual_long_position_number + 1

                        # 如果某个账号的持有期货的数量已经到达最大持有期货的数量，则跳过这个账号
                        if actual_long_position_number >= should_long_position_number or total_hold_commodity_future_number >= MultiFactorConfig.Max_Hold_Commodity_Future_Number:
                            Logger.info("账号[%s]的持有期货的数量已经到达[%d]只，不再开仓期货", quant2_account.account_name, total_hold_commodity_future_number)
                            break

                # 开空仓
                if waiting_short_quant2_commodity_future_filter_list is not None and len(waiting_short_quant2_commodity_future_filter_list) > 0 \
                        and should_short_quant2_commodity_future_filter_list is not None and len(should_short_quant2_commodity_future_filter_list) > 0:
                    for waiting_quant2_commodity_future_filter in waiting_short_quant2_commodity_future_filter_list:

                        # 如果备选的期货不在应该持有的期货中，则跳过
                        if waiting_quant2_commodity_future_filter.code not in list(map(lambda x: x.code, should_short_quant2_commodity_future_filter_list)):
                            continue

                        # 如果当前持仓的期货已经持有，则跳过
                        if holding_quant2_commodity_future_transaction_record_list is not None and len(holding_quant2_commodity_future_transaction_record_list) > 0:
                            if waiting_quant2_commodity_future_filter.code in list(map(lambda x: x.code, holding_quant2_commodity_future_transaction_record_list)):
                                continue

                        # 查询commodity_future_info记录（大小写不敏感的精确匹配查询）
                        filter_dict = {'code__iexact': CommodityFutureCodeUtil.code_to_contract_code(waiting_quant2_commodity_future_filter.code)}
                        commodity_future_info: CommodityFutureInfo = self.commodity_future_info_dao.find_one(filter_dict, dict(), list())

                        # 查询期货记录
                        filter_dict = {'code': waiting_quant2_commodity_future_filter.code, 'transaction_date': transaction_date}
                        commodity_future_date_contract_data: CommodityFutureDateContractData = self.commodity_future_date_contract_data_dao.find_one(filter_dict, dict(), list())
                        if commodity_future_date_contract_data is None:
                            Logger.warning("期货在日期[%s]没有交易记录，从表quant2_c_f_filter表中删除这条记录，跳过这个期货", waiting_quant2_commodity_future_filter.code, transaction_date)

                            # 从quant2_c_f_filter表中删除这条记录
                            # self.quant2_commodity_future_filter_dao.delete(quant2_commodity_future_filter)

                            continue

                        # 根据持仓时间，判断k线形态；判断当天是否应当平仓和开仓
                        k_line_movement_pattern, close_or_open_position = self.quant2_k_line_movement_pattern_and_close_or_open_position_by_holding_position_time_and_commodity_future_date_contract_data(transaction_date, commodity_future_date_contract_data)
                        if close_or_open_position is False:
                            Logger.info("期货[%s]在日期[%s]不应当平仓和开仓", commodity_future_date_contract_data.code, transaction_date)
                            continue

                        # 如果期货的收盘价过高，或者资金资产太少，连一手也无法开仓，则直接查找下一个账号
                        if FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, 1) > quant2_account.capital_assets:
                            break

                        # 如果可以开仓，则向quant2_c_f_transact_record表中插入数据，从quant2_c_f_filter表中删除这条记录。
                        # 计算应该开仓期货的数量
                        # quant2_c_f_filter表的记录数（up_down_percentage_with_n_date字段小于0）
                        quant2_c_f_filter_number: int = -1
                        filter_dict = {'up_down_percentage_with_n_date__lt': 0}
                        quant2_c_f_filter_number = len(self.quant2_commodity_future_filter_dao.find_list(filter_dict, dict(), dict()))

                        # 应当开仓的期货的数量
                        should_open_position_commodity_future_number: int = -1
                        if quant2_c_f_filter_number <= (should_short_position_number - actual_short_position_number):
                            should_open_position_commodity_future_number = quant2_c_f_filter_number
                        else:
                            should_open_position_commodity_future_number = should_short_position_number - actual_short_position_number

                        # 判断是否需要继续开仓
                        if should_open_position_commodity_future_number == 0:
                            Logger.info("账号[%s]持有期货的数量已经是%d，不再需要开仓期货", quant2_account.account_name, should_short_position_number)
                            break

                        # 计算开仓多少手
                        # 开仓多少手
                        init_open_or_close_position_lot: int = FeeUtil.calculate_open_commodity_future_lot(commodity_future_date_contract_data.close_price, commodity_future_info, float(quant2_account.capital_assets) / float(MultiFactorConfig.Max_Hold_Commodity_Future_Number - quant2_account.hold_commodity_future_number))
                        open_or_close_position_lot: int = init_open_or_close_position_lot if init_open_or_close_position_lot > 1 else 1
                        while float(quant2_account.capital_assets) / float(MultiFactorConfig.Max_Hold_Commodity_Future_Number - quant2_account.hold_commodity_future_number) >= FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, open_or_close_position_lot):
                            if FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, open_or_close_position_lot) >= float(quant2_account.capital_assets) / MultiFactorConfig.Max_Hold_Commodity_Future_Number:
                                break
                            open_or_close_position_lot = open_or_close_position_lot + 1
                        if float(quant2_account.capital_assets) / float(MultiFactorConfig.Max_Hold_Commodity_Future_Number - quant2_account.hold_commodity_future_number) < FeeUtil.calculate_open_commodity_future_cost_and_fee(commodity_future_date_contract_data.close_price, commodity_future_info, open_or_close_position_lot):
                            if open_or_close_position_lot == 1:
                                continue
                            else:
                                open_or_close_position_lot = open_or_close_position_lot - 1

                        # 向表quant2_c_f_transact_record中插入记录
                        self.quant2_commodity_future_transaction_record_dao.insert_when_open_short_position(quant2_account.account_name, waiting_quant2_commodity_future_filter.code,
                            transaction_date, commodity_future_date_contract_data.close_price, open_or_close_position_lot, commodity_future_info, Direction.Down, k_line_movement_pattern)

                        # 删除quant2_c_f_filter表中的记录
                        # self.quant2_commodity_future_filter_dao.delete(quant2_commodity_future_filter)

                        # 更新账号的持有期货的数量
                        total_hold_commodity_future_number = total_hold_commodity_future_number + 1
                        actual_short_position_number = actual_short_position_number + 1

                        # 如果某个账号的持有期货的数量已经到达最大持有期货的数量，则跳过这个账号
                        if actual_short_position_number >= should_short_position_number or total_hold_commodity_future_number >= MultiFactorConfig.Max_Hold_Commodity_Future_Number:
                            Logger.info("账号[%s]的持有期货的数量已经到达[%d]只，不再开仓期货",quant2_account.account_name, total_hold_commodity_future_number)
                            break
        else:
            Logger.warning("表quant2_account为空")

        super().next(responsibility_chain_dto)